package com.hexun.dubbo.utils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hexun.dubbo.JsonMixins;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.concurrent.ConcurrentHashMap;

/**
 * JacksonUtils
 *
 * @author yuanyue
 */
public class JacksonUtils {
    /**
     * serialize
     *
     * @param containerRequestContext  ContainerRequestContext
     * @param containerResponseContext ContainerResponseContext
     * @throws IOException
     */
    public static String serialize(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
        Annotation[] entityAnnotations = containerResponseContext.getEntityAnnotations();
        JsonMixins anno = null;
        if (entityAnnotations != null) {
            for (Annotation annotation : entityAnnotations) {
                if (annotation instanceof JsonMixins) {
                    anno = (JsonMixins) annotation;
                    break;
                }
            }
        }
        ObjectMapper mapper = getMapper(anno);
        Object entity = containerResponseContext.getEntity();
        if (entity != null) {
            return mapper.writeValueAsString(entity);
        }
        return null;
    }

    /**
     * get mapper from cache by annotation
     *
     * @param anno JsonMixins
     * @return ObjectMapper
     */
    private static ObjectMapper getMapper(JsonMixins anno) {
        if (anno == null) {
            return getDefaultMapper();
        }
        ObjectMapper objectMapper = map.get(anno);
        if (objectMapper == null) {
            objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
            if (anno.mixins().length != anno.targets().length) {
                return objectMapper;
            }
            for (int i = 0; i < anno.mixins().length; i++) {
                objectMapper.addMixInAnnotations(anno.targets()[i], anno.mixins()[i]);
            }
            map.put(anno, objectMapper);
        }
        return objectMapper;
    }


    /**
     * @return ObjectMapper
     */
    private static ObjectMapper getDefaultMapper() {
        if (objectMapper == null) {
            objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        }
        return objectMapper;
    }

    private static ObjectMapper objectMapper = null;
    private static ConcurrentHashMap<JsonMixins, ObjectMapper> map = new ConcurrentHashMap<>();

    /**
     * 可能是json
     *
     * @param string
     * @return
     */
    public static boolean mayBeJSON(String string) {
        return string != null
                && ("null".equals(string)
                || (string.startsWith("[") && string.endsWith("]"))
                || (string.startsWith("{") && string.endsWith("}")));
    }


    /**
     * copy from: org.json, org.json.JSONObject.quote(String string)
     */
    public static String quote(String string) {
        if (string == null || string.length() == 0) {
            return "\"\"";
        }

        char b;
        char c = 0;
        int i;
        int len = string.length();
        StringBuffer sb = new StringBuffer(len + 4);
        String t;

        sb.append('"');
        for (i = 0; i < len; i += 1) {
            b = c;
            c = string.charAt(i);
            switch (c) {
                case '\\':
                case '"':
                    sb.append('\\');
                    sb.append(c);
                    break;
                case '/':
                    if (b == '<') {
                        sb.append('\\');
                    }
                    sb.append(c);
                    break;
                case '\b':
                    sb.append("\\b");
                    break;
                case '\t':
                    sb.append("\\t");
                    break;
                case '\n':
                    sb.append("\\n");
                    break;
                case '\f':
                    sb.append("\\f");
                    break;
                case '\r':
                    sb.append("\\r");
                    break;
                default:
                    if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
                            || (c >= '\u2000' && c < '\u2100')) {
                        t = "000" + Integer.toHexString(c);
                        sb.append("\\u" + t.substring(t.length() - 4));
                    } else {
                        sb.append(c);
                    }
            }
        }
        sb.append('"');
        return sb.toString();
    }
}
